package com.example.yy.usbcameratest;


import android.app.AlertDialog;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.os.Environment;
import android.os.Handler;
import android.text.ParcelableSpan;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.style.ForegroundColorSpan;
import android.text.style.StyleSpan;
import android.util.AttributeSet;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

import com.arcsoft.face.ErrorInfo;
import com.arcsoft.face.FaceEngine;
import com.arcsoft.face.FaceInfo;
import com.arcsoft.face.LivenessInfo;
import com.arcsoft.face.LivenessParam;
import com.arcsoft.face.enums.DetectModel;
import com.arcsoft.imageutil.ArcSoftImageFormat;
import com.arcsoft.imageutil.ArcSoftImageUtil;
import com.arcsoft.imageutil.ArcSoftImageUtilError;
import com.example.yy.usbcameratest.model.UsbResolution;
import com.example.yy.usbcameratest.util.SharedUtil;
import com.example.yy.usbcameratest.view.SelectDialog;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import android.content.DialogInterface;
import android.widget.Toast;

public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback, Runnable {
    FaceEngine faceEngine ;
    private static final boolean DEBUG = true;
    private static final String TAG = "CameraPreview";
    protected Context context;
    private SurfaceHolder holder;
    Thread mainLoop = null;

    private boolean isOpenEncrypt =  SharedUtil.getValue(getContext().getApplicationContext(),SharedUtil.encrypt,true, SharedUtil.ShareType.BOOLEAN) ;
    private Bitmap bmp = null;

    private boolean cameraExists = false;
    private boolean shouldStop = false;

    // /dev/videox (x=cameraId+cameraBase) is used.
    // In some omap devices, system uses /dev/video[0-3],
    // so users must use /dev/video[4-].
    // In such a case, try cameraId=0 and cameraBase=4
    private int cameraId = 0;
    private int cameraBase = 0;

    // This definition also exists in ImageProc.h.
    // Webcam must support the resolution 640x480 with YUYV format.
    private int IMG_WIDTH = 1920;
    private int IMG_HEIGHT = 1080;

    // The following variables are used to draw camera images.
    private int winWidth = 0;
    private int winHeight = 0;
    private Rect rect;
    private int dw, dh;
    private float rate;
    private boolean pause = false;

    private AlertDialog.Builder alertDialogBuilder;
    private AlertDialog alertDialog;

    // JNI functions
    public native void setenv();

    public native int prepareCamera(int videoid, int []resolution, boolean ay_camera);

    public native int prepareCameraWithBase(int videoid, int camerabase, int []resolution, boolean ay_camera);

    public native int processCamera();

    public native void stopCamera();

    public native void pixeltobmp(Bitmap bitmap);
    final SpannableStringBuilder notificationSpannableStringBuilder = new SpannableStringBuilder();
    final StringBuilder stringBuilder = new StringBuilder();
    public native String getPixelFormat();
    List<FaceInfo> faceInfoList = new ArrayList<>();

    public native UsbCameraInfo[] getUsbCameraInfo();

    public native int getUsbCameraVersion();

    public native String getUsbCameraSn();

    private ScheduledExecutorService scheduledExecutorService =Executors.newSingleThreadScheduledExecutor();

    static {
        System.loadLibrary( "ImageProc" );
    }

    public void transferFaceEngine(FaceEngine faceEngine) {
        this.faceEngine = faceEngine;
    }

    public void setResolution(int width, int height) {
            cameraExists = false;
            IMG_WIDTH = width;
            IMG_HEIGHT = height;

    }

    public interface FeedbackListener{
        void feedbackTxt(SpannableStringBuilder stringBuilder);
    }
    private  FeedbackListener feedbackListener;
    public void setFeedbackTxtListener(FeedbackListener listener) {
        this.feedbackListener = listener;
    }

    class SavePic implements Runnable {
        int name;
        byte []ax;
        public SavePic (int name, ByteBuffer bb) {
            this.name = name;
            this.ax = new byte[bb.array().length];
            System.arraycopy(bb.array(), 0, this.ax, 0, bb.array().length);
        }

        @Override
        public void run() {
            Bitmap bitmap = Bitmap.createBitmap(IMG_WIDTH, IMG_HEIGHT, Bitmap.Config.ARGB_8888);
            ByteBuffer bf = ByteBuffer.wrap(ax);
            bitmap.copyPixelsFromBuffer(bf);
            final String parent = Environment.getExternalStorageDirectory().getAbsolutePath()+"/usbCamera_arcsoft/";
            File fileParent = new File(parent);
            if (!fileParent.exists()){
                fileParent.mkdirs();
            }
            File file = new File(parent, "usbpic" + name + ".jpg");
            if (file.exists())
                file.delete();
            try {
                file.createNewFile();
                OutputStream os = new FileOutputStream(file);
                bitmap.compress(Bitmap.CompressFormat.JPEG, 100, os);
                os.close();
                notificationSpannableStringBuilder.clear();
                addNotificationInfo(notificationSpannableStringBuilder, null, file.getName()+"  bitmap 保存成功!");
                // 图像对齐
                Bitmap mBitmap = ArcSoftImageUtil.getAlignedBitmap(bitmap, true);


                showNotificationAndFinish(notificationSpannableStringBuilder);

                if (mBitmap == null) {
                    Log.i(TAG,"mBitmap == null");
                    addNotificationInfo(notificationSpannableStringBuilder, null, file.getName()+"  bitmap is null!");
                    showNotificationAndFinish(notificationSpannableStringBuilder);
                    return;
                }
                int width = mBitmap.getWidth();
                int height = mBitmap.getHeight();
                byte[] bgr24 = ArcSoftImageUtil.createImageData(mBitmap.getWidth(), mBitmap.getHeight(), ArcSoftImageFormat.BGR24);
                int transformCode = ArcSoftImageUtil.bitmapToImageData(mBitmap, bgr24, ArcSoftImageFormat.BGR24);
                if (transformCode != ArcSoftImageUtilError.CODE_SUCCESS) {
                    Log.e(TAG, "transform failed, code is " + transformCode);
                    addNotificationInfo(notificationSpannableStringBuilder, new StyleSpan(Typeface.BOLD), "transform bitmap To ImageData failed", "code is ", String.valueOf(transformCode), "\n");
                    showNotificationAndFinish(notificationSpannableStringBuilder);
                    return;
                }
                if (faceEngine != null){
                    Log.i(TAG,"detectFaces");
                    int detectCode = faceEngine.detectFaces(bgr24, width, height, FaceEngine.CP_PAF_BGR24, DetectModel.RGB, faceInfoList);
                    if (detectCode != ErrorInfo.MOK){
                        Log.i(TAG,"detect faces failed!!");
                    }

                    //绘制bitmap
                    Bitmap bitmapForDraw = bitmap.copy(Bitmap.Config.RGB_565, true);
                    Canvas canvas = new Canvas(bitmapForDraw);
                    Paint paint = new Paint();

                    if (faceInfoList.size()>0){

                        for (int i = 0; i < faceInfoList.size(); i++) {
                            //绘制人脸框
                            paint.setStyle(Paint.Style.STROKE);
                            canvas.drawRect(faceInfoList.get(i).getRect(), paint);
                            //绘制人脸序号
                            paint.setStyle(Paint.Style.FILL_AND_STROKE);
                            int textSize = faceInfoList.get(i).getRect().width() / 2;
                            paint.setTextSize(textSize);

                            canvas.drawText(String.valueOf(i), faceInfoList.get(i).getRect().left, faceInfoList.get(i).getRect().top, paint);
                        }

                        //显示
                        final Bitmap finalBitmapForDraw = bitmapForDraw;



                        int faceProcessCode = faceEngine.process(bgr24, width, height, FaceEngine.CP_PAF_BGR24, faceInfoList, FaceEngine.ASF_AGE | FaceEngine.ASF_GENDER | FaceEngine.ASF_FACE3DANGLE | FaceEngine.ASF_LIVENESS);

                        if (faceProcessCode != ErrorInfo.MOK) {
                            addNotificationInfo(notificationSpannableStringBuilder, new ForegroundColorSpan(Color.RED), "process failed! code is ", String.valueOf(faceProcessCode), "\n");
                        } else {
                        }

                        //活体检测结果
                        List<LivenessInfo> livenessInfoList = new ArrayList<>();
                        int livenessCode = faceEngine.getLiveness(livenessInfoList);

                        if (livenessCode != ErrorInfo.MOK){
                            addNotificationInfo(notificationSpannableStringBuilder, null, "at least one of livenessCode detect failed!,codes are:",
                                    String.valueOf(livenessCode));
                            showNotificationAndFinish(notificationSpannableStringBuilder);
                            return;
                        }
                        stringBuilder.delete(0,stringBuilder.length());
                        //活体检测数据
                        if (livenessInfoList.size() > 0) {

                            addNotificationInfo(notificationSpannableStringBuilder, null, "each face:");

                            for (int i = 0; i < livenessInfoList.size(); i++) {
                                String liveness = null;
                                switch (livenessInfoList.get(i).getLiveness()) {
                                    case LivenessInfo.ALIVE:
                                        liveness = "ALIVE";
                                        break;
                                    case LivenessInfo.NOT_ALIVE:
                                        liveness = "NOT_ALIVE";
                                        break;
                                    case LivenessInfo.UNKNOWN:
                                        liveness = "UNKNOWN";
                                        break;
                                    case LivenessInfo.FACE_NUM_MORE_THAN_ONE:
                                        liveness = "FACE_NUM_MORE_THAN_ONE";
                                        break;
                                    default:
                                        liveness = "UNKNOWN";
                                        break;
                                }
                                addNotificationInfo(notificationSpannableStringBuilder, null, " face[", String.valueOf(i), "]:", liveness, "");
                                stringBuilder.append(" face["+String.valueOf(i)+"]:"+ liveness);
                            }
                            new Thread(new Runnable() {
                                @Override
                                public void run() {
                                    File file1 = new File(parent, "usbpic_"+stringBuilder + name + ".jpg");
                                    if (file1.exists())
                                        file1.delete();
                                    try {
                                        file1.createNewFile();
                                        OutputStream os1 = new FileOutputStream(file1);
                                        finalBitmapForDraw.compress(Bitmap.CompressFormat.JPEG, 100, os1);
                                        os1.close();
                                    }catch (IOException e){

                                    }
                                }
                            }).start();
                        }else{
                            new Thread(new Runnable() {
                                @Override
                                public void run() {
                                    File file1 = new File(parent, "usbpic_face_nolive" + name + ".jpg");
                                    if (file1.exists())
                                        file1.delete();
                                    try {
                                        file1.createNewFile();
                                        OutputStream os1 = new FileOutputStream(file1);
                                        finalBitmapForDraw.compress(Bitmap.CompressFormat.JPEG, 100, os1);
                                        os1.close();
                                    }catch (IOException e){

                                    }
                                }
                            }).start();
                            addNotificationInfo(notificationSpannableStringBuilder, null, " livenessInfoList.size()<=0");
                        }
                        showNotificationAndFinish(notificationSpannableStringBuilder);
                    }else{

                        addNotificationInfo(notificationSpannableStringBuilder, null, " 未检测到人脸");
                        showNotificationAndFinish(notificationSpannableStringBuilder);
                    }
                }else{
                    addNotificationInfo(notificationSpannableStringBuilder, null,"虹软未初始化");
                    showNotificationAndFinish(notificationSpannableStringBuilder);
                }

            } catch (IOException e) {

            }
        }
    }

    private void showNotificationAndFinish(SpannableStringBuilder notificationSpannableStringBuilder) {
        if (feedbackListener != null){
            feedbackListener.feedbackTxt(notificationSpannableStringBuilder);
        }
    }

    /**
     * 追加提示信息
     *
     * @param stringBuilder 提示的字符串的存放对象
     * @param styleSpan     添加的字符串的格式
     * @param strings       字符串数组
     */
    private void addNotificationInfo(SpannableStringBuilder stringBuilder, ParcelableSpan styleSpan, String... strings) {
        if (stringBuilder == null || strings == null || strings.length == 0) {
            return;
        }
        int startLength = stringBuilder.length();
        for (String string : strings) {
            stringBuilder.append(string);
        }
        int endLength = stringBuilder.length();
        if (styleSpan != null) {
            stringBuilder.setSpan(styleSpan, startLength, endLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        }
    }
    public CameraPreview(Context context) {
        super( context );
        this.context = context;
        if (DEBUG)
            Log.d( TAG, "CameraPreview constructed" );
        setFocusable( true );

        holder = getHolder( );
        holder.addCallback( this );
        holder.setType( SurfaceHolder.SURFACE_TYPE_NORMAL );
    }

    public CameraPreview(Context context, AttributeSet attrs) {
        super( context, attrs );
        this.context = context;
        if (DEBUG)
            Log.d( TAG, "CameraPreview constructed" );
        setFocusable( true );

        holder = getHolder( );
        holder.addCallback( this );
        holder.setType( SurfaceHolder.SURFACE_TYPE_NORMAL );
    }

    static int a = 0;

    public int javaGetDeviceGO(){

        return a++;
    }

    @Override
    public void run() {
        boolean reload = false;
        long tm = 0;
        int yny = 0;

        handlerPause.postDelayed(runnablePause, 60000);
        setenv();

        for (long x = 0; x < 1; x++) {
            UsbCameraInfo[] array = getUsbCameraInfo();
            for (int i = 0; i < array.length; i++) {
                Log.d(TAG, "CameraInfo name = " + array[i].pixelFormat + " format num = " + array[i].resolutionTable.length);

                for (int j = 0; j < array[i].resolutionTable.length; j++) {
                    Log.d(TAG, "J = " + j);
                    for (int k = 0; k < array[i].resolutionTable[j].length; k++) {
                        Log.d(TAG, "CameraInfo resolutionTable = " + i + " " + j + " " + array[i].resolutionTable[j][k]);
                    }
                }
            }
        }

        Log.d(TAG, "getPixelFormat = " + getPixelFormat());

        while (true && cameraExists) {
            // obtaining display area to draw a large image
            if (winWidth == 0) {
                winWidth = this.getWidth( );
                winHeight = this.getHeight( );

                Log.d(TAG, "winWidth " + winWidth + " winHeight " + winHeight);

                if (winWidth * 3 / 4 <= winHeight) {
                    dw = 0;
                    dh = (winHeight - winWidth * 3 / 4) / 2;
                    rate = ((float) winWidth) / IMG_WIDTH;
                    rect = new Rect( dw, dh, dw + winWidth - 1, dh + winWidth * 3 / 4 - 1 );
                } else {
                    dw = (winWidth - winHeight * 4 / 3) / 2;
                    dh = 0;
                    rate = ((float) winHeight) / IMG_HEIGHT;
                    rect = new Rect( dw, dh, dw + winHeight * 4 / 3 - 1, dh + winHeight - 1 );
                }
            }

            if (reload) {
                int []resolution = {IMG_WIDTH, IMG_HEIGHT};
                int res = prepareCameraWithBase(0,0, resolution, isOpenEncrypt);
                if (res == 0) {
                    Log.i(TAG," run : 正常打开");
                    showDialog("摄像头正常打开");
//                if (prepareCameraWithBase(0,0, resolution, isOpenEncrypt) != -1) {
                    reload = false;
                }else{
                    if (res == -1){
                        showDialog("请插入usb摄像头");
                    }else if (res == -2){
                        showDialog("该摄像头不支持加密");
                    }
                }
                if (reload)
                    continue;
            }

            // obtaining a camera image (pixel data are stored in an array in JNI).
            if (processCamera( ) == -1) {
                Log.d(TAG, "readframeonce timeout");
                stopCamera();
                reload = true;
                continue;
            }

            // camera image to bmp
            pixeltobmp( bmp );

            if (System.currentTimeMillis() - tm > 5000) {
                Log.d(TAG, "SavePic");

                tm = System.currentTimeMillis();
                ByteBuffer buffer = ByteBuffer.allocate(bmp.getWidth() * bmp.getHeight() * 4);
                bmp.copyPixelsToBuffer(buffer);
                new Thread(new SavePic(yny++, buffer)).start();
            }

            Canvas canvas = getHolder( ).lockCanvas( );
            if (canvas != null) {
                // draw camera bmp on canvas
                canvas.drawBitmap( bmp, null, rect, null );

                getHolder( ).unlockCanvasAndPost( canvas );
            }

            if (shouldStop) {
                shouldStop = false;
                break;
            }

            if (pause) {
                pause = false;
                Log.d(TAG, "PAUSE");
                stopCamera();
                reload = true;

                try {
                    Thread.sleep(2000);
                    handlerPause.postDelayed(runnablePause, 60000);
                } catch (InterruptedException e) {

                }
            }
        }
    }

    int []resolution = new int[2];
    static List<UsbResolution>  usbResolutions = new ArrayList<>();
            static{
                usbResolutions.add(new UsbResolution(320,240));
                usbResolutions.add(new UsbResolution(640,480));
                usbResolutions.add(new UsbResolution(1280,720));
            }
    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        if (DEBUG)
            Log.d( TAG, "surfaceCreated" );
        int res= SharedUtil.getValue(context,SharedUtil.USB_RESOLUTION,-1, SharedUtil.ShareType.INTEGER);
        Log.i(TAG,"res=========="+res);

        if (res != SelectDialog.SELECT_NONE){
            IMG_WIDTH = usbResolutions.get(res).getWidth();
            IMG_HEIGHT = usbResolutions.get(res).getHeight();
        }

      resolution[0]  = IMG_WIDTH;
        resolution[1] = IMG_HEIGHT;

        // /dev/videox (x=cameraId + cameraBase) is used

        final int ret = prepareCameraWithBase( cameraId, cameraBase, resolution, isOpenEncrypt);
        if (ret !=0){
            if (ret == -1){
                showDialog("打开失败，请确保插入USB摄像头");
            }else if (ret == -2){
                showDialog("改摄像头不支持加密");
            }
        }
        IMG_WIDTH = resolution[0];
        IMG_HEIGHT = resolution[1];

        Log.d(TAG, "打开摄像头返回 " + ret);
        Log.d(TAG, "摄像头实际分辨率 " + IMG_WIDTH + " * " + IMG_HEIGHT);

        if (ret == 0){


            if (bmp == null) {
                bmp = Bitmap.createBitmap( IMG_WIDTH, IMG_HEIGHT, Bitmap.Config.ARGB_8888 );
            }

            cameraExists = true;

        mainLoop = new Thread( this );
        mainLoop.start( );
         }else if (ret !=0){

            scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
                @Override
                public void run() {
                 int    ret1 = prepareCameraWithBase( cameraId, cameraBase, resolution, isOpenEncrypt);
                 Log.i(TAG,"2syici,ret1="+ret1);
                 if (ret1 !=0){
                     if (ret1 == -2){

                                    Log.i(TAG,"不是加密");
                                    showDialog("该摄像头不支持加密");

                     }else{

                                    showDialog("请插入USB摄像头");

                     }
                 }else{

                        showDialog("摄像头正常打开");
                        getHandler().post(new Runnable() {
                            @Override
                            public void run() {
                                if (bmp == null) {
                                    bmp = Bitmap.createBitmap( IMG_WIDTH, IMG_HEIGHT, Bitmap.Config.ARGB_8888 );
                                }

                                cameraExists = true;

                                mainLoop = new Thread( CameraPreview.this );
                                mainLoop.start( );
                                Log.i(TAG,"停掉周期性打开=");
                            }
                        });

                     scheduledExecutorService.shutdownNow();
                 }
                }
            },2,2, TimeUnit.SECONDS);


        }



            }

    private void showDialog(final String message ) {
                getHandler().post(new Runnable() {
                    @Override
                    public void run() {
                        if (alertDialogBuilder == null){
                            alertDialogBuilder = new AlertDialog.Builder(context);
                        }
                        if (alertDialog != null && alertDialog.isShowing()){
                            alertDialog.dismiss();
                            alertDialog = null;
                        }

                        alertDialog = alertDialogBuilder.create();
                        alertDialog.setMessage(message);
                        alertDialog.show();

                    }
                });

    }



    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        if (DEBUG)
            Log.d( TAG, "surfaceChanged" );
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        if (DEBUG)
            Log.d( TAG, "surfaceDestroyed" );
        if (cameraExists) {
            shouldStop = true;
            while (shouldStop) {
                try {
                    Thread.sleep( 100 ); // wait for thread stopping
                } catch (Exception e) {
                }
            }
        }
        stopCamera( );
    }

    public void MJPGToRGBA(byte []mjpg, byte []rgba) {
        if (mjpg.length == 0)
            return;

        Bitmap bitmap = BitmapFactory.decodeByteArray(mjpg, 0, mjpg.length);

        if (bitmap != null) {
            int width, height;

            width=bitmap.getWidth();
            height=bitmap.getHeight();

//            Log.d(TAG, "width = " + width + " height = " + height);

            ByteBuffer buffer=ByteBuffer.allocate(width * height * 4);
            bitmap.copyPixelsToBuffer(buffer);

            byte []out = buffer.array();

            System.arraycopy(out, 0, rgba, 0, out.length);
        } else {
            Log.e(TAG, "MJPG data error");
        }
    }

    Handler handlerPause = new Handler();
    Runnable runnablePause = new Runnable() {
        @Override
        public void run() {
//            pause = true;
        }
    };
}
